package com.r2coding.server.modules.file.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.r2coding.core.constants.Constants;
import com.r2coding.core.exception.Pan4jException;
import com.r2coding.core.utils.FileUtils;
import com.r2coding.core.utils.IdUtil;
import com.r2coding.server.common.event.log.ErrorLogEvent;
import com.r2coding.server.modules.file.context.FileChunkMergeAndSaveContext;
import com.r2coding.server.modules.file.context.FileSaveContext;
import com.r2coding.server.modules.file.entity.File;
import com.r2coding.server.modules.file.entity.FileChunk;
import com.r2coding.server.modules.file.mapper.FileMapper;
import com.r2coding.server.modules.file.service.IFileChunkService;
import com.r2coding.server.modules.file.service.IFileService;
import com.r2coding.storage.engine.core.StorageEngine;
import com.r2coding.storage.engine.core.context.DeleteFileContext;
import com.r2coding.storage.engine.core.context.MergeFileContext;
import com.r2coding.storage.engine.core.context.StoreFileContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.assertj.core.util.Lists;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author 98k灬
 * @version v1.0.0
 * @Description : 功能描述
 * @Create on : 2023/10/12 22:15
 **/
@Service
@Slf4j
public class IFileServiceImpl extends ServiceImpl<FileMapper, File> implements IFileService, ApplicationContextAware {
    @Autowired
    private StorageEngine storageEngine;

    private ApplicationContext applicationContext;

    @Autowired
    private IFileChunkService fileChunkService;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /**
     * 保存单文件
     *
     * @param fileSaveContext 保存单文件的上下文实体
     */
    @Override
    public void saveFile(FileSaveContext fileSaveContext) {
        this.storeMultipartFile(fileSaveContext);
        File file = this.doSaveFile(fileSaveContext.getFilename(),
                fileSaveContext.getRealPath(),
                fileSaveContext.getTotalSize(),
                fileSaveContext.getIdentifier(),
                fileSaveContext.getUserId());
        fileSaveContext.setRecord(file);
    }

    /**
     * 合并物理文件并保存文件
     *
     * @param context 上下文实体
     */
    @Override
    public void mergeFileChunkAndSaveFile(FileChunkMergeAndSaveContext context) {
        this.doMergeFileChunk(context);
        File file = this.doSaveFile(context.getFilename(), context.getRealPath(), context.getTotalSize(), context.getIdentifier(), context.getUserId());
        context.setRecord(file);
    }

    /****************************************** private *********************************************/

    private void doMergeFileChunk(FileChunkMergeAndSaveContext context) {
        LambdaQueryWrapper<FileChunk> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(FileChunk::getIdentifier, context.getIdentifier())
                .eq(FileChunk::getCreateUser, context.getUserId())
                .ge(FileChunk::getExpirationTime, new Date());
        List<FileChunk> fileChunks = fileChunkService.list(wrapper);
        if (CollectionUtils.isEmpty(fileChunks)) {
            throw new Pan4jException("该文件未找到分片记录");
        }
        List<String> realPathList = fileChunks.stream()
                .sorted(Comparator.comparing(FileChunk::getChunkNumber))
                .map(FileChunk::getRealPath).collect(Collectors.toList());
        try {
            MergeFileContext mergeFileContext = new MergeFileContext();
            BeanUtils.copyProperties(context, mergeFileContext);
            mergeFileContext.setRealPathList(realPathList);
            storageEngine.mergeFile(mergeFileContext);
            context.setRealPath(mergeFileContext.getRealPath());
        } catch (IOException exception) {
            throw new Pan4jException("文件合并失败");
        }

        List<Long> fileChunkIdList = fileChunks.stream().map(FileChunk::getId).collect(Collectors.toList());
        fileChunkService.removeByIds(fileChunkIdList);
    }

    private File doSaveFile(String filename, String realPath, Long totalSize, String identifier, Long userId) {
        File file = File.builder()
                .fileId(IdUtil.get())
                .fileSize(String.valueOf(totalSize))
                .fileSizeDesc(FileUtils.byteCountToDisplaySize(totalSize))
                .filename(filename)
                .identifier(identifier)
                .createUser(userId)
                .realPath(realPath)
                .createTime(new Date())
                .build();
        int result = this.baseMapper.insert(file);
        if (result != Constants.ONE_INT) {
            try {
                DeleteFileContext deleteFileContext = DeleteFileContext.builder()
                        .realFilePathList(Lists.newArrayList(realPath)).build();
                storageEngine.delete(deleteFileContext);
            } catch (IOException exception) {
                exception.printStackTrace();
                ErrorLogEvent errorLogEvent = new ErrorLogEvent(this, "文件物理删除失败，请执行手动删除！文件路径: " + realPath, userId);
                applicationContext.publishEvent(errorLogEvent);
                throw new Pan4jException("文件删除失败");
            }
        }
        return file;
    }

    private void storeMultipartFile(FileSaveContext context)  {
        try {
            StoreFileContext storeFileContext = StoreFileContext.builder()
                .inputStream(context.getFile().getInputStream())
                .filename(context.getFilename())
                .totalSize(context.getTotalSize())
                .build();
            storageEngine.store(storeFileContext);
            context.setRealPath(storeFileContext.getRealPath());
        } catch (IOException exception) {
            exception.printStackTrace();
            throw new Pan4jException("文件上传失败");
        }
    }
}
